#include <GSMCommandResponse.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <system.h>

GSMCommandResponse::GSMCommandResponse(DataInput* input, DataOutput* output) :
		dataInput(input), dataOutput(output)
{}

uint16_t GSMCommandResponse::sendString(const char* string) {
	uint16_t result = strlen(string);
	uint16_t sent = 0;

	while(sent<result) {
		if (dataOutput->tryWrite(string[sent])) {
			sent++;
		}
		/*
		while(!dataOutput->freeSpace());
		dataOutput->write(string[sent++]);
		*/
	}
	return result;
}

uint16_t GSMCommandResponse::sendCommand(const char* command) {
	/*
	 * Flush the incoming buffer for remains of previous responses.
	 * This also kills URCs so if those are desired, this needs to be changed.
	 */
	dataInput->clear();

	uint16_t result = sendString(command);

	/*
	 * Wait for space for trailing CR and send it.
	 */
	/*
	while(!dataOutput->freeSpace());
	dataOutput->write(13);
	*/
	while (!dataOutput->tryWrite(13));

	return result +1;
}

int8_t GSMCommandResponse::executeCommand(char* command, char* response, uint32_t timeout) {
	if (command[0] != 'A')
		return -1;	// Error, we don't understand commands that don't begin with "AT...."

	/*
	 * Calculate how many echo characters to ignore.
	 * Some GSM module manuals recommend that echo is left on. Receiving the first
	 * character A of a command as echo guarantees that no URCs appear until the
	 * complete response of the command is received.
	 */
	uint16_t expectedEchoLength = sendCommand(command);
	bool prevWasWS = false;

	uint16_t receivedEchoCount = 0;		// the command echo.
	uint16_t receivedResponseCount = 0;	// beyond the command echo.

	while(getSystemTimeMillis()<timeout) {
		// We have to: Consume the echo and get the response.
		if (dataInput->available()) {
			char data = dataInput->read();
			if (receivedEchoCount==0 && data!='A') {
				return -2; 	// Error, the first data to be received was not the initial 'A' of the command, we are out of sync.
			}

			bool isWS = (data==10 || data==13);

			if (receivedEchoCount==expectedEchoLength+2) { // the extra 2 are the initial whitespace of the response (skipped).
				response[receivedResponseCount++] = data;
				if(isWS && prevWasWS) {
					// Check for "<cr><lf>OK<cr><lf> or <cr><lf>ERROR<cr><lf>"
					if (receivedResponseCount >= 4 && strncmp(response+receivedResponseCount-4,"OK", 2)==0) {
						// Terminate string.
						response[receivedResponseCount-2] = 0;
						return 0;
					} else if(receivedResponseCount >= 7 && strncmp(response+receivedResponseCount-7,"ERROR", 5)==0) {
						// Terminate string.
						response[receivedResponseCount-2] = 0;
						return 1;
					}
				}
			} else {
				receivedEchoCount++;
			}
			prevWasWS = isWS;
		}
	}
	return -3; // timeout
}

/*
 * Read a phonebook entry.
 */
bool parsePhonebookEntry(char* text, PhonebookEntry* result) {
	int startOfNumber = 0;

	while(text[startOfNumber]!=0 && text[startOfNumber]!=',') startOfNumber++;
	if (text[startOfNumber]==0) return false;
	startOfNumber = startOfNumber+2;

	int endOfNumber = startOfNumber+1;
	while(text[endOfNumber]!=0 && text[endOfNumber]!='"') endOfNumber++;
	if (text[endOfNumber]==0) return false;

	memcpy(result->number, text+startOfNumber, endOfNumber-startOfNumber);
	result->number[endOfNumber-startOfNumber] = 0;

	int startOfType = endOfNumber+2;
	int temp;
	sscanf(text+startOfType, "%d", &temp);
	result->numberKind = temp;

	int startOfName = startOfType;
	while(text[startOfName]!=0 && text[startOfName]!='"') startOfName++;
	startOfName += 1; // skip the quote.
	int endOfName = startOfName+1;
	while(text[endOfName]!=0 && text[endOfName]!='"') endOfName++;
	if (text[endOfName]==0) return false;

	memcpy(result->name, text+startOfName, endOfName-startOfName);
	result->name[endOfName-startOfName] = 0;

	return true;
}

/*
 * This is useful for setting a default phone number: Put the SIM card in a phone
 * and store a phonebook entry on the card. The number can then be looked up under
 * the name here.
 * The implementation finds the first alphabetical record whose first name letter
 * matches that of the name searched for. Then the book is traversed from there.
 * Older GSM modules support only this and have no random search.
 */
bool GSMCommandResponse::getPhonebookNumber(const char* name, PhonebookEntry* result) {
	uint32_t timeout = getSystemTimeMillis() + 2000UL;
	if (executeCommand((char*)"AT+CPBS=SM", tempResponse, timeout)) return false; // Select SIM card.

	sleep(100);

	timeout = getSystemTimeMillis() + 2000UL;
	sprintf(tempCommand, "AT^SPBC=%c", name[0]); // Get index of first match
	if (executeCommand(tempCommand, tempResponse, timeout)) return false;
	int index;
	sscanf(tempResponse+7, "%d", &index);

	// Search through the sorted records for the one matching the name exactly.
	while(true) {
		sleep(100);
		timeout = getSystemTimeMillis() + 2000UL;
		sprintf(tempCommand, "AT^SPBG=%d", index);
		if (executeCommand(tempCommand, tempResponse, timeout)) return false; 	// ERROR returned
		if (!parsePhonebookEntry(tempResponse, result)) return false;			// Cannot parse
		if (strcasecmp(name,result->name)==0) return true;	// match.
		index++;
	}
	return false;  // unreachable really.
}

/*
 * Send an SMS.
 */
bool GSMCommandResponse::sendSMS(SMS& sms) {
	uint32_t timeout = getSystemTimeMillis() + 2000UL;
	executeCommand((char*)"AT+CMGF=1", tempResponse, timeout);
	sleep(100);
	sprintf(tempCommand, "AT+CMGS=\"%s\"", sms.number);
	timeout = getSystemTimeMillis() + 2000UL;
	sendCommand(tempCommand);

	// Now expecting a "<cr><lf>> " but we will just wait 1000ms instead of
	// looking for that.
	sleep(1000);
	timeout = getSystemTimeMillis() + 5000UL;
	sendString(sms.text);

	/*
	while(!dataOutput->freeSpace());
	dataOutput->write(26);
	*/
	while (!dataOutput->tryWrite(26) && getSystemTimeMillis()<timeout); // CTRL-Z

	return true;
}

bool parseSMS(char* text, SMS& sms) {
	uint8_t index=0;

	while(text[index]!=':' && text[index]!=0) index++;
	if(text[index]==0)
		return false;

	sscanf(text+index+1, "%d", &sms.index);

	uint8_t numCommas=0;
	while(numCommas<2) {
		if(text[index]==',') numCommas++;
		if(text[index]==0)
			return false;
		index++;
	}

	uint8_t numberBeginIndex = index+1;
	uint8_t closeQuoteIndex = numberBeginIndex;

	while(text[closeQuoteIndex]!='"' && text[closeQuoteIndex]!=0) closeQuoteIndex++;
	if(text[closeQuoteIndex]==0)
		return false;

	memcpy(sms.number, text+numberBeginIndex, closeQuoteIndex-numberBeginIndex);
	sms.number[closeQuoteIndex-numberBeginIndex] = 0;

	// Skip to next line.
	index=closeQuoteIndex;
	uint8_t numCRLFs=0;
	while (numCRLFs<2) { // 2???
		if(text[index]==10 || text[index]==13) numCRLFs++;
		if(text[index]==0)
			return false;
		index++;
	}

	int length = strlen(text)-index-6; // the 6 are removal of terminating <CR><LF>OK<CR><LF>
	memcpy(sms.text, text+index, length);
	sms.text[length]=0;

	return true;
}

/*
 * Check for an unread SMS, and read it and immediately delete it from the phone/card storage.
 */
bool GSMCommandResponse::newReceivedSMS(SMS& sms) {
	uint32_t timeout = getSystemTimeMillis() + 1000UL;
	executeCommand((char*)"AT+CMGF=1", tempResponse, timeout);
	sleep(100);

	uint32_t now = getSystemTimeMillis();
	timeout = getSystemTimeMillis() + 2000UL;

	if (executeCommand((char*)"AT+CMGL", tempResponse, timeout)) {
		return false;
	}

	bool result = parseSMS(tempResponse, sms);
	if (result) {
		sprintf(tempCommand, "AT+CMGD=%d", sms.index); // Delete the SMS immediately. But ignore failure.
		sleep(100);
		timeout =  getSystemTimeMillis() + 2000UL;
		executeCommand(tempCommand, tempResponse, timeout);
	}
	return result;
}
